#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.

using Crux.Core.StackExchangeRedis;
using MessagePack;
using Microsoft.Extensions.Logging;
using Nito.AsyncEx;
using Snappier;
using StackExchange.Redis;

namespace Crux.Core.Cache.Extensions;

/// <summary>
/// HybridCacheProvider
/// </summary>
public class HybridCacheProvider(
        IOptions<HybridCacheOption> options,
        IMemoryCache memoryCache,
        IRedisOperator redisOperator,
        ILockProvider lockProvider,
        ILogger<HybridCacheProvider> logger)
    : IHybridCacheProvider
{
    #region private fields
    private HybridCacheOption _option => options.Value;
    private TimeSpan SlidingExpiration => _option?.SlidingExpiration ?? TimeSpan.FromSeconds(10);
    private TimeSpan AbsoluteExpiration => _option?.AbsoluteExpiration ?? TimeSpan.FromHours(1);
    private int RandomSlidingSecond => _option?.RandomSlidingSecond ?? 0;

    #endregion private fields
    #region private methods
    private bool TryGetValueFromMemory<T>(
        string cacheKey,
        out T? value,
        CancellationToken cancellationToken = default)
    {
        if (!memoryCache.TryGetValue<byte[]>(cacheKey, out var compressedBuffer))
        {
            if (logger.IsEnabled(LogLevel.Trace))
                logger.LogTrace($"Invoke TryGetValueFromMemory，缓存中不存在值,cacheKey:{cacheKey}");

            value = default;
            return false;
        }

        try
        {
            if (compressedBuffer is null || 0 == compressedBuffer.Length)
            {
                value = default;
                return false;
            }

            var buffer = Snappy.DecompressToArray(compressedBuffer);

            value = MessagePackUtils.Deserialize<T>(buffer, cancellationToken: cancellationToken);
            if (logger.IsEnabled(LogLevel.Trace))
                logger.LogTrace($"成功序列化字符串，cacheKey:{cacheKey}");

            Array.Clear(buffer, 0, buffer.Length);
            buffer = null;

            return true;
        }
        catch (MessagePackSerializationException e)
        {
            if (logger.IsEnabled(LogLevel.Error))
                logger.LogError(e, e.Message);

            value = default;
            return false;
        }
    }

    /// <inheritdoc/>
    private bool TryGetValue<T>(string cacheKey, out T? value, CancellationToken cancellationToken = default)
    {
        if (TryGetValueFromMemory(cacheKey, out value) && value is not null)
        {
            if (logger.IsEnabled(LogLevel.Trace))
                logger.LogTrace($"Invoke TryGetValue，缓存中存在值,cacheKey:{cacheKey}");
            return true;
        }

        byte[]? buffer = redisOperator.StringGet(cacheKey);
        if (buffer is null || 0 == buffer.Length)
        {
            if (logger.IsEnabled(LogLevel.Trace))
                logger.LogTrace($"分布式缓存不存在该值，cacheKey:{cacheKey}，返回默认值(对象为null)");
            value = default;

            return false;
        }

        try
        {
            cancellationToken.ThrowIfCancellationRequested();

            value = MessagePackUtils.Deserialize<T>(buffer, cancellationToken: cancellationToken);

            if (logger.IsEnabled(LogLevel.Trace))
                logger.LogTrace($"成功序列化字符串，重新设置内存变量,cacheKey:{cacheKey}");

            if (!_option.WithMemory) return true;

            var compressedBuffer = Snappy.CompressToArray(buffer);

            var slidingExpiration = _option.SlidingExpiration ?? SlidingExpiration;

            memoryCache.Set(cacheKey, compressedBuffer, slidingExpiration);

            return true;
        }
        catch (MessagePackSerializationException e)
        {
            if (logger.IsEnabled(LogLevel.Error))
                logger.LogError(e, message: e?.Message);

            if (logger.IsEnabled(LogLevel.Trace))
                logger.LogTrace($"序列化数据失败，删除分布式缓存和本地缓存,cacheKey:{cacheKey}");

            redisOperator.KeyDelete(cacheKey, flags: CommandFlags.FireAndForget);
            memoryCache.Remove(cacheKey);

            value = default;

            return false;
        }
    }

    private TimeSpan? GetExpiration(TimeSpan? expiration)
    {
        if (expiration is not null)
        {
            return expiration;
        }

        if (_option.AbsoluteExpiration is not null)
        {
            return _option.AbsoluteExpiration;
        }

        var slidingSecond = RandomUtils.Next(RandomSlidingSecond);

        var slidingSecondSpan = TimeSpan.FromSeconds(slidingSecond);

        var defaultExpiration = AbsoluteExpiration.Add(slidingSecondSpan);

        return defaultExpiration;
    }

    #endregion private methods

    #region implements IHybridCacheProvider
    /// <inheritdoc/>
    public bool Exists(string cacheKey)
    {
        return AsyncContext.Run(() => ExistsAsync(cacheKey));
    }

    /// <inheritdoc/>
    public async Task<bool> ExistsAsync(string cacheKey, CancellationToken cancellationToken = default)
    {
        if (memoryCache.TryGetValue<byte[]>(cacheKey, out _))
        {
            if (logger.IsEnabled(LogLevel.Trace))
                logger.LogTrace($"Invoke Exists,内存中存在缓存,cacheKey:{cacheKey}");
            return true;
        }

        var state = await redisOperator.KeyExistsAsync(cacheKey).ConfigureAwait(false);
        if (state)
        {
            if (logger.IsEnabled(LogLevel.Trace))
                logger.LogTrace($"分布式缓存存在键值,cacheKey:{cacheKey}");
        }
        else
        {
            if (logger.IsEnabled(LogLevel.Trace))
                logger.LogTrace($"分布式缓存不存在键值,cacheKey:{cacheKey}");
        }

        return state;
    }

    /// <inheritdoc/>
    public T? Get<T>(string cacheKey)
    {
        return AsyncContext.Run(() => GetAsync<T>(cacheKey));
    }

    /// <inheritdoc/>
    public async Task<T?> GetAsync<T>(string cacheKey, CancellationToken cancellationToken = default)
    {
        return await Task.Run(() =>
        {
            var state = TryGetValue<T>(cacheKey, out var value, cancellationToken);
            if (logger.IsEnabled(LogLevel.Trace))
                logger.LogTrace($"Invoke Get,cacheKey:{cacheKey},state:{state}");

            return value;
        }, cancellationToken).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public T? GetOrCreate<T>(string cacheKey, Func<HybridCacheOption, T> dataRetriever)
    {
        return AsyncContext.Run(
            () => GetOrCreateAsync(cacheKey, (option) => Task.Run(() => dataRetriever(option))));
    }

    /// <inheritdoc/>
    public async Task<T?> GetOrCreateAsync<T>(string cacheKey, Func<HybridCacheOption, Task<T>> dataRetriever,
        CancellationToken cancellationToken = default)
    {
        if (TryGetValue<T>(cacheKey, out var value, cancellationToken))
        {
            if (logger.IsEnabled(LogLevel.Trace))
                logger.LogTrace($"Invoke GetOrCreateAsync,存在值,cacheKey:{cacheKey}");
            return value;
        }

        int retryCount = 0;
        bool lockState = false;

        try
        {
            while (!(lockState = await lockProvider.LockTakeAsync(cacheKey).ConfigureAwait(false))
                   && retryCount++ < 5)
            {
                await Task.Delay(10, cancellationToken).ConfigureAwait(false);

                if (retryCount % 2 == 0)
                {
                    if (TryGetValueFromMemory<T>(cacheKey, out value, cancellationToken) && value is not null)
                    {
                        return value;
                    }
                }
                else if (TryGetValue<T>(cacheKey, out value, cancellationToken) && value is not null)
                {
                    return value;
                }
            }

            if (logger.IsEnabled(LogLevel.Trace))
                logger.LogTrace($"是否获取到锁，lockState:{lockState}，cacheKey:{cacheKey}");

            if (!lockState)
            {
                if (TryGetValue<T>(cacheKey, out value) && value is not null)
                    return value;

                var option = new HybridCacheOption();
                var data = await dataRetriever.Invoke(option);

                return data;
            }

            {
                var option = new HybridCacheOption();

                var data = await dataRetriever.Invoke(option);
                if (data is null) return data;

                var buffer = await MessagePackUtils.SerializeAsync(data, cancellationToken: cancellationToken).ConfigureAwait(false);
                if (logger.IsEnabled(LogLevel.Trace))
                    logger.LogTrace($"设置值，cacheKey:{cacheKey}");

                var expiration = GetExpiration(option?.AbsoluteExpiration);

                var state = await redisOperator.StringSetAsync(
                    cacheKey,
                    buffer,
                    expiration,
                    when: When.Always,
                    flags: CommandFlags.None).ConfigureAwait(false);
                if (logger.IsEnabled(LogLevel.Trace))
                    logger.LogTrace($"设置redis:{state}");

                if (state && (option?.WithMemory ?? false))
                {
                    byte[] compressedBuffer = Snappy.CompressToArray(buffer);

                    var slidingExpiration = option.SlidingExpiration ?? SlidingExpiration;
                    memoryCache.Set(cacheKey, compressedBuffer, slidingExpiration);
                }

                Array.Clear(buffer, 0, buffer.Length);
                buffer = null;

                return data;
            }
        }
        catch (Exception e)
        {
            if (logger.IsEnabled(LogLevel.Error))
                logger.LogError(e, e.Message);

            memoryCache.Remove(cacheKey);

            return default;
        }
        finally
        {
            if (lockState)
            {
                await lockProvider.LockReleaseAsync(cacheKey).ConfigureAwait(false);
            }
        }
    }

    /// <inheritdoc/>
    public void Remove(string cacheKey)
    {
        AsyncContext.Run(() => RemoveAsync(cacheKey));
    }

    /// <inheritdoc/>
    public async Task RemoveAsync(string cacheKey, CancellationToken cancellationToken = default)
    {
        await redisOperator.KeyDeleteAsync(cacheKey, flags: CommandFlags.FireAndForget).ConfigureAwait(false);

        memoryCache.Remove(cacheKey);
    }

    /// <inheritdoc/>
    public bool TryRemove<T>(string cacheKey, out T? value)
    {
        bool existsState = false;

        try
        {
            if (existsState = TryGetValueFromMemory(cacheKey, out value))
            {
                return true;
            }

            if (!Exists(cacheKey))
            {
                value = default;

                return false;
            }

            value = Get<T>(cacheKey);

            existsState = true;

            return true;
        }
        finally
        {
            if (existsState)
                Remove(cacheKey);
        }
    }

    /// <inheritdoc/>
    public void Set<T>(string cacheKey, T cacheValue, TimeSpan? expiration = null)
    {
        AsyncContext.Run(() => SetAsync<T>(cacheKey, cacheValue, expiration));
    }

    /// <inheritdoc/>
    public async Task SetAsync<T>(
        string cacheKey,
        T cacheValue,
        TimeSpan? expiration = null,
        CancellationToken cancellationToken = default)
    {
        var buffer = MessagePackUtils.Serialize(cacheValue, cancellationToken: cancellationToken);
        if (logger.IsEnabled(LogLevel.Trace))
            logger.LogTrace($"设置值，cacheKey:{cacheKey}");

        expiration = GetExpiration(expiration);

        var state = await redisOperator.StringSetAsync(
            cacheKey,
            buffer,
            expiration,
            when: When.Always,
            flags: CommandFlags.None).ConfigureAwait(false);

        if (state && _option.WithMemory)
        {
            var compressedBuffer = Snappy.CompressToArray(buffer);

            var slidingExpiration = _option.SlidingExpiration ?? SlidingExpiration;

            memoryCache.Set(cacheKey, compressedBuffer, slidingExpiration);
        }

        Array.Clear(buffer, 0, buffer.Length);
        buffer = null;
    }

    /// <inheritdoc/>
    public bool TrySet<T>(
        string cacheKey,
        T cacheValue,
        bool forceUpdate = false,
        TimeSpan? expiration = null)
    {
        return AsyncContext.Run(() => TrySetAsync(cacheKey, cacheValue, forceUpdate, expiration));
    }

    /// <inheritdoc/>
    public async Task<bool> TrySetAsync<T>(
        string cacheKey,
        T cacheValue,
        bool forceUpdate = false,
        TimeSpan? expiration = null,
        CancellationToken cancellationToken = default)
    {
        var retryCount = 0;
        var lockState = false;

        try
        {
            while (!(lockState = await lockProvider.LockTakeAsync(cacheKey).ConfigureAwait(false))
                   && retryCount++ < 5)
            {
                await Task.Delay(10, cancellationToken).ConfigureAwait(false);

                if (retryCount % 2 == 0)
                {
                    if (TryGetValueFromMemory<T>(cacheKey, out var value, cancellationToken) && value is not null)
                    {
                        return false;
                    }
                }
                else if (TryGetValue<T>(cacheKey, out var value, cancellationToken) && value is not null)
                {
                    return false;
                }
            }

            if (!lockState) return false;

            var buffer = await MessagePackUtils.SerializeAsync(cacheValue, cancellationToken: cancellationToken).ConfigureAwait(false);

            expiration = GetExpiration(expiration);

            var when = forceUpdate ? When.Always : When.NotExists;

            var state = await redisOperator.StringSetAsync(
                cacheKey,
                buffer,
                expiration,
                when: when,
                flags: CommandFlags.None).ConfigureAwait(false);
            if (state && _option.WithMemory)
            {
                byte[] compressedBuffer = Snappy.CompressToArray(buffer);

                memoryCache.Set(cacheKey, compressedBuffer, _option.SlidingExpiration ?? SlidingExpiration);
            }

            Array.Clear(buffer, 0, buffer.Length);
            buffer = null;

            return state;
        }
        catch (Exception e)
        {
            if (logger.IsEnabled(LogLevel.Error))
                logger.LogError(e, e.Message);

            memoryCache.Remove(cacheKey);

            return default;
        }
        finally
        {
            if (lockState)
                await lockProvider.LockReleaseAsync(cacheKey);
        }
    }

    #endregion implements IHybridCacheProvider
}
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.